home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 2000 August: Tool Chest / Dev.CD Aug 00 TC Disk 2.toast / pc / sample code / quicktime / all macintosh / music and sound / qtimadecompression / source / imadecompression.c next >
Encoding:
C/C++ Source or Header  |  2000-06-23  |  17.8 KB  |  653 lines

  1. /*
  2. **    Apple Macintosh Developer Technical Support
  3. **
  4. **    Routines demonstrating how to play IMA compressed WAVE files
  5. **    using a combination of QuickTime and the Sound Manager.
  6. **
  7. **    by Mark Cookson, Apple Developer Technical Support
  8. **
  9. **    File:    IMADecompression.c
  10. **
  11. **    Copyright ©1998-1999 Apple Computer, Inc.
  12. **    All rights reserved.
  13. **
  14. **    You may incorporate this sample code into your applications without
  15. **    restriction, though the sample code has been provided "AS IS" and the
  16. **    responsibility for its operation is 100% yours.  However, what you are
  17. **    not permitted to do is to redistribute the source as "Apple Sample
  18. **    Code" after having made changes. If you're going to re-distribute the
  19. **    source, we require that you make it clear in the source that the code
  20. **    was descended from Apple Sample Code, but that you've made changes.
  21. */
  22.  
  23. #include <Memory.h>
  24. #include <QuickDraw.h>
  25. #include <Fonts.h>
  26. #include <Windows.h>
  27. #include <Menus.h>
  28. #include <TextEdit.h>
  29. #include <Dialogs.h>
  30. #include <Sound.h>
  31. #include <SoundInput.h>
  32. #include <Files.h>
  33. #include <Endian.h>
  34. #include <Navigation.h>
  35.  
  36. #include <stdio.h>
  37. #include "SoundStruct.h"
  38. #include "Wave.h"
  39.  
  40. typedef struct adpcmcoef_tag {
  41.     short    iCoef1;
  42.     short    iCoef2;
  43. } ADPCMCOEFSET;
  44.  
  45. typedef struct waveformat_extended_tag {
  46.     short        wFormatTag;            /* format type */
  47.     short        nChannels;            /* number of channels (i.e. mono, stereo...) */
  48.     long        nSamplesPerSec;        /* sample rate */
  49.     long        nAvgBytesPerSec;    /* for buffer estimation */
  50.     short        nBlockAlign;        /* block size of data */
  51.     short        wBitsPerSample;        /* Number of bits per sample of mono data */
  52.     short        cbSize;                /* The count in bytes of the extra size */
  53. } WAVEFORMATEX;
  54.  
  55. typedef struct adpcmwaveformat_tag {
  56.     WAVEFORMATEX    wfx;
  57.     short            wSamplesPerBlock;
  58.     short            wNumCoef;
  59.     ADPCMCOEFSET    aCoef[32];
  60. } ADPCMWAVEFORMAT;
  61.  
  62. typedef struct {
  63.     long            atomSize;            // how big this structure is (big endian)
  64.     long            atomType;            // atom type - always kMicrosoftADPCMFormat (big endian)
  65.     // everything below here is little endian - right out of the wave header
  66.     ADPCMWAVEFORMAT    adpcm;
  67. } AtomMSADPCMWaveFormatEx;
  68.  
  69. typedef struct {
  70.     AudioFormatAtom                formatData;
  71.     AtomMSADPCMWaveFormatEx        endianData;
  72.     AudioTerminatorAtom            terminatorData;
  73. } AudioCompressionAtom, *AudioCompressionAtomPtr, **AudioCompressionAtomHandle;
  74.  
  75. // Prototypes
  76. OSErr InstallRequiredAppleEvents (void);
  77. pascal OSErr HandleOApp (AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon);
  78. pascal OSErr HandleODoc (AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon);
  79. pascal OSErr HandlePDoc (AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon);
  80. pascal OSErr HandleQuit (AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon);
  81. OSErr GetSoundToPlay (FSSpec *fileToPlay);
  82. OSErr PlaySound (FSSpec *fileToPlay);
  83.  
  84. // Globals
  85. SoundInfo                theSoundInfo;
  86. SndCommand                playCmd1,
  87.                         playCmd2,
  88.                         callCmd;
  89. SndCommand*                playCmd                = nil;
  90. CmpSoundHeader            WAVESndHeader1,
  91.                         WAVESndHeader2;
  92. CmpSoundHeaderPtr        WAVESndHeader        = nil;
  93. SndChannelPtr            soundChan            = nil;
  94. SoundConverter            sc                    = nil;
  95. SndCallBackUPP            SoundCallBackFcnUPP    = nil;
  96. Ptr                        decomBuf            = nil,
  97.                         decomBuf1            = nil,
  98.                         decomBuf2            = nil,
  99.                         WAVEBuffer            = nil,
  100.                         compressedBuf        = nil;
  101. unsigned long            inputFrames            = 0,
  102.                         framesConverted        = 0,
  103.                         totalFrames            = 0,
  104.                         bytesConverted        = 0,
  105.                         inputBytes            = 0,
  106.                         outputFrames        = 0,
  107.                         outputBytes            = 0,
  108.                         gSleepTime            = 60;
  109. long                    length                = 0;
  110. short                    whichBuffer            = 0;
  111. Boolean                    gBufferDone            = false,
  112.                         Quitting            = false,
  113.                         gSoundPlaying        = false;
  114.  
  115. static pascal void    SoundCallBackFcn (SndChannelPtr theChannel, SndCommand *theCmd) {
  116. #pragma unused (theChannel)
  117.     #if !GENERATINGCFM
  118.         long        oldA5;
  119.         oldA5 = SetA5 (theCmd->param2);
  120.     #endif
  121.  
  122.     gBufferDone = true;
  123.  
  124.     #if !GENERATINGCFM
  125.         oldA5 = SetA5 (oldA5);
  126.     #endif
  127. }
  128.  
  129. static OSErr MenuBarInit (void) {
  130.     Handle                    menuBar;
  131.     MenuHandle                menu;
  132.     OSErr                    err                = noErr;
  133.  
  134.     menuBar = GetNewMBar (128);
  135.     if (menuBar != nil) {
  136.         SetMenuBar (menuBar);
  137.         menu = GetMenuHandle (128);
  138.         if (menu != nil) {
  139.             AppendResMenu (menu, 'DRVR');
  140.             DrawMenuBar ();
  141.         } else {
  142.             err = memFullErr;
  143.         }
  144.     } else {
  145.         err = memFullErr;
  146.     }
  147.  
  148.     return err;
  149. }
  150.  
  151. static OSErr DispatchMenuChoice (long menuChoice) {
  152.     OSErr                        err                    = noErr;
  153.     short                        menu;
  154.     short                        item;
  155.     MenuHandle                    appleMenu;
  156.     Str255                        accName;
  157.     short                        accNumber;
  158.     FSSpec                        fileToPlay;
  159.  
  160.     if (menuChoice != 0) {
  161.         menu = HiWord (menuChoice);
  162.         item = LoWord (menuChoice);
  163.         switch (menu) {
  164.             case 128:    // Apple Menu
  165.                 appleMenu = GetMenuHandle (128);
  166.                 GetMenuItemText (appleMenu, item, accName);
  167.                 accNumber = OpenDeskAcc (accName);
  168.                 break;
  169.             case 129:    // File Menu
  170.                 switch (item) {
  171.                     case 1:    // Open
  172.                         err = GetSoundToPlay (&fileToPlay);
  173.                         break;
  174.                     case 3:    // Quit
  175.                         Quitting = true;
  176.                         break;
  177.                 }
  178.         }
  179.     }
  180.  
  181.     HiliteMenu (0);
  182.  
  183.     return err;
  184. }
  185.  
  186. static void DoIdle (void) {
  187.     Boolean            cleanup        = false;
  188.     OSErr            err            = noErr;
  189.  
  190.     if (gSoundPlaying == true && gBufferDone == true) {
  191.         if (whichBuffer == 1) {
  192.             playCmd = &playCmd1;
  193.             decomBuf = decomBuf1;
  194.             WAVESndHeader = &WAVESndHeader1;
  195.             whichBuffer = 2;
  196.         } else {
  197.             playCmd = &playCmd2;
  198.             decomBuf = decomBuf2;
  199.             WAVESndHeader = &WAVESndHeader2;
  200.             whichBuffer = 1;
  201.         }
  202.  
  203.         if (bytesConverted < length) {
  204.             if (bytesConverted + inputBytes > length) {
  205.                 inputBytes = length - bytesConverted;
  206.                 inputFrames = totalFrames - framesConverted;
  207.                 if (inputFrames == 0)
  208.                     inputFrames = 1;
  209.             }
  210.  
  211.             BlockMoveData (WAVEBuffer + bytesConverted, compressedBuf, inputBytes);
  212.  
  213.             err = SoundConverterConvertBuffer (sc, compressedBuf, inputFrames, decomBuf, &outputFrames, &outputBytes);
  214.             framesConverted += inputFrames;
  215.             bytesConverted += inputBytes;
  216.             WAVESndHeader->numFrames = outputFrames;
  217.  
  218.             gBufferDone = false;
  219.             err = SndDoCommand (soundChan, &callCmd, true);            // Reuse callBackCmd.
  220.             err = SndDoCommand (soundChan, playCmd, true);            // Play the next buffer.
  221.         } else {
  222.             err = SoundConverterEndConversion (sc, decomBuf, &outputFrames, &outputBytes);
  223.             WAVESndHeader->numFrames = outputFrames;
  224.  
  225.             err = SndDoCommand (soundChan, playCmd, true);            // Play the last buffer.
  226.             gSoundPlaying = false;
  227.             cleanup = true;
  228.         }
  229.     }
  230.  
  231.     if (cleanup == true) {
  232.         if (sc != nil) {
  233.             err = SoundConverterClose (sc);
  234.         }
  235.  
  236.         if (soundChan != nil)
  237.             err = SndDisposeChannel (soundChan, false);    // wait until sounds stops playing before disposing of channel
  238.         if (theSoundInfo.refNum)
  239.             FSClose (theSoundInfo.refNum);
  240.         if (SoundCallBackFcnUPP)
  241.             DisposeRoutineDescriptor (SoundCallBackFcnUPP);
  242.         if (decomBuf1)
  243.             DisposePtr (decomBuf1);
  244.         if (decomBuf2)
  245.             DisposePtr (decomBuf2);
  246.         if (WAVEBuffer)
  247.             DisposePtr (WAVEBuffer);
  248.         if (compressedBuf)
  249.             DisposePtr (compressedBuf);
  250.  
  251.         soundChan            = nil;
  252.         decomBuf            = nil;
  253.         decomBuf1            = nil;
  254.         decomBuf2            = nil;
  255.         WAVEBuffer            = nil;
  256.         compressedBuf        = nil;
  257.         gSleepTime            = 60;
  258.     }
  259. }
  260.  
  261. void main (void) {
  262.     OSErr                        err                    = noErr;
  263.     Boolean                        gotEvent;
  264.     EventRecord                    event;
  265.     WindowPtr                    window;
  266.     short                        thePart;
  267.  
  268.     MaxApplZone ();
  269.  
  270.     InitGraf (&qd.thePort);
  271.     InitFonts ();
  272.     InitWindows ();
  273.     InitMenus ();
  274.     TEInit ();
  275.     InitDialogs ((long)nil);
  276.     InitCursor ();
  277.  
  278.     err = InstallRequiredAppleEvents ();
  279.  
  280.     err = MenuBarInit ();
  281.  
  282.     while (!Quitting) {
  283.         gotEvent = WaitNextEvent (everyEvent, &event, gSleepTime, nil);
  284.  
  285.         if (gotEvent) {
  286.             switch (event.what) {
  287.                 case kHighLevelEvent:
  288.                     err = AEProcessAppleEvent (&event);
  289.                     break;
  290.                 case mouseDown:
  291.                     thePart = FindWindow (event.where, &window);
  292.                     switch (thePart) {
  293.                         case inMenuBar:
  294.                             DispatchMenuChoice (MenuSelect (event.where));
  295.                             break;
  296.                     }
  297.                     break;
  298.                 case keyDown:
  299.                     if (event.modifiers & cmdKey) {
  300.                         err = DispatchMenuChoice (MenuKey (event.message & charCodeMask));
  301.                     }
  302.                     break;
  303.             }
  304.         } else {
  305.             DoIdle ();
  306.         }
  307.     }
  308. }
  309.  
  310. OSErr GetSoundToPlay (FSSpec *fileToPlay) {
  311.     OSErr                    err                    = noErr;
  312.     SFTypeList                typeList            = {'WAVE', 'wav ', 0, 0};
  313.     StandardFileReply        sfReply;
  314.  
  315.     if (NavServicesAvailable () == true) {
  316.         NavReplyRecord                navReply;
  317.         NavDialogOptions            dialogOptions;
  318.  
  319.         err = NavGetDefaultDialogOptions (&dialogOptions);
  320.         if (err == noErr) {
  321.             dialogOptions.dialogOptionFlags = kNavAllFilesInPopup;
  322.         }
  323.  
  324.         if (err == noErr) {
  325.             err = NavGetFile (nil, &navReply, &dialogOptions, nil, nil, nil, nil, nil);
  326.         }
  327.  
  328.         if (navReply.validRecord && err == noErr) {
  329.             ProcessSerialNumber            processSN        = {0, kCurrentProcess};
  330.             AEAddressDesc                targetAddress    = {typeNull, nil};
  331.             AppleEvent                    theODOC            = {typeNull, nil},
  332.                                         theReply        = {typeNull, nil};
  333.  
  334.             // Create an Apple Event to ourselves.
  335.             err = AECreateDesc (typeProcessSerialNumber, &processSN, sizeof (ProcessSerialNumber), &targetAddress);
  336.  
  337.             if (err == noErr) {
  338.                 // Create the open document event.
  339.                 err = AECreateAppleEvent (kCoreEventClass, kAEOpenDocuments, &targetAddress, kAutoGenerateReturnID, kAnyTransactionID, &theODOC);
  340.                 AEDisposeDesc (&targetAddress);
  341.             }
  342.  
  343.             if (err == noErr) {
  344.                 // Put the list of files into the open document event Apple Event.
  345.                 err = AEPutParamDesc (&theODOC, keyDirectObject, &(navReply.selection));
  346.             }
  347.  
  348.             if (err == noErr) {
  349.                 // Send the open document event to ourselves.
  350.                 err = AESend (&theODOC, &theReply, kAENoReply, kAENormalPriority, kAEDefaultTimeout, nil, nil);
  351.                 AEDisposeDesc (&theODOC);
  352.                 AEDisposeDesc (&theReply);
  353.             }
  354.  
  355.         }
  356.  
  357.         (void)NavDisposeReply (&navReply);
  358.     } else {
  359.         StandardGetFile (nil, 2, typeList, &sfReply);
  360.  
  361.         if (sfReply.sfGood == true) {
  362.             *fileToPlay = sfReply.sfFile;
  363.             err = PlaySound (fileToPlay);
  364.         } else {
  365.             err = userCanceledErr;
  366.         }
  367.     }
  368.  
  369.     return err;
  370. }
  371.  
  372. OSErr PlaySound (FSSpec *fileToPlay) {
  373.     OSErr                    err                    = noErr;
  374.     AudioCompressionAtom    decomAtom;
  375.     SoundComponentData        inputFormat,
  376.                             outputFormat;
  377.     OSType                    waveFormat;
  378.     unsigned long            targetBytes            = 25000;
  379.  
  380.     gSleepTime = 1;
  381.  
  382.     if (err == noErr) {
  383.         err = FSpOpenDF (fileToPlay, fsRdPerm, &theSoundInfo.refNum);
  384.  
  385.         if (err == noErr) {
  386.             // Parse the WAVE file and get the 'fmt ' atom.
  387.             err = ASoundGetWAVEHeader (&theSoundInfo, &length, (fmtChunk*)&(decomAtom.endianData.adpcm));
  388.         }
  389.  
  390.         if (err == noErr) {
  391.             // Figure out which type of ADPCM file we have.
  392.             switch (EndianU16_LtoN (decomAtom.endianData.adpcm.wfx.wFormatTag)) {
  393.                 case 0x0002:
  394.                     waveFormat = kMicrosoftADPCMFormat;
  395.                     break;
  396.                 case 0x0011:
  397.                     waveFormat = kDVIIntelIMAFormat;
  398.                     break;
  399.                 default:
  400.                     err = badFormat;
  401.             }
  402.         }
  403.     }
  404.  
  405.     if (err == noErr) {
  406.         inputFormat.flags = 0;
  407.         inputFormat.format = waveFormat;
  408.         inputFormat.numChannels = EndianU16_LtoN (decomAtom.endianData.adpcm.wfx.nChannels);
  409.         inputFormat.sampleSize = EndianU16_LtoN (decomAtom.endianData.adpcm.wfx.wBitsPerSample);
  410.         inputFormat.sampleRate = (EndianU32_LtoN (decomAtom.endianData.adpcm.wfx.nSamplesPerSec)) << 16;
  411.         inputFormat.sampleCount = 0;
  412.         inputFormat.buffer = nil;
  413.         inputFormat.reserved = 0;
  414.  
  415.         outputFormat.flags = 0;
  416.         outputFormat.format = kSoundNotCompressed;
  417.         outputFormat.numChannels = EndianU16_LtoN (decomAtom.endianData.adpcm.wfx.nChannels);
  418.         outputFormat.sampleSize = 16;
  419.         outputFormat.sampleRate = (EndianU32_LtoN (decomAtom.endianData.adpcm.wfx.nSamplesPerSec)) << 16;
  420.         outputFormat.sampleCount = 0;
  421.         outputFormat.buffer = nil;
  422.         outputFormat.reserved = 0;
  423.  
  424.         err = SoundConverterOpen (&inputFormat, &outputFormat, &sc);
  425.     }
  426.  
  427.     if (err == noErr) {
  428.         // Make atom to send to ADPCM decompressor so it knows how to decompress the data.
  429.         decomAtom.formatData.size = sizeof (AudioFormatAtom);
  430.         decomAtom.formatData.atomType = kAudioFormatAtomType;
  431.         decomAtom.formatData.format = waveFormat;
  432.  
  433.         decomAtom.endianData.atomSize = sizeof (AtomMSADPCMWaveFormatEx);
  434.         decomAtom.endianData.atomType = waveFormat;
  435.  
  436.         decomAtom.terminatorData.size = sizeof (AudioTerminatorAtom);
  437.         decomAtom.terminatorData.atomType = kAudioTerminatorAtomType;
  438.  
  439.         err = SoundConverterSetInfo (sc, siDecompressionParams, &decomAtom);
  440.     }
  441.  
  442.     if (err == noErr) {
  443.         // Find out how many frames are in the entire sound.
  444.         err = SoundConverterGetBufferSizes (sc, length * 4, &totalFrames, &inputBytes, &outputBytes);
  445.     }
  446.  
  447.     if (err == noErr) {
  448.         do {
  449.             targetBytes *= 2;
  450.             err = SoundConverterGetBufferSizes (sc, targetBytes, &inputFrames, &inputBytes, &outputBytes);
  451.         } while (err == notEnoughBufferSpace && targetBytes < (MaxBlock () / 4));
  452.     }
  453.  
  454.     if (err == noErr) {
  455.         WAVEBuffer = NewPtr (length);
  456.         err = MemError ();
  457.     }
  458.  
  459.     if (err == noErr) {
  460.         decomBuf1 = NewPtr (outputBytes);
  461.         err = MemError ();
  462.     }
  463.  
  464.     if (err == noErr) {
  465.         decomBuf2 = NewPtr (outputBytes);
  466.         err = MemError ();
  467.     }
  468.  
  469.     if (err == noErr) {
  470.         compressedBuf = NewPtr (inputBytes);
  471.         err = MemError ();
  472.     }
  473.  
  474.     if (err == noErr) {
  475.         err = SetFPos (theSoundInfo.refNum, fsFromStart, theSoundInfo.dataStart);
  476.     }
  477.  
  478.     if (err == noErr) {
  479.         err = FSRead (theSoundInfo.refNum, &length, WAVEBuffer);
  480.     }
  481.  
  482.     if (err == noErr) {
  483.         BlockMoveData (WAVEBuffer, compressedBuf, inputBytes);
  484.         err = SoundConverterBeginConversion (sc);
  485.     }
  486.  
  487.     if (err == noErr) {
  488.         bytesConverted = 0;
  489.         framesConverted = 0;
  490.         err = SoundConverterConvertBuffer (sc, compressedBuf, inputFrames, decomBuf1, &outputFrames, &outputBytes);
  491.         bytesConverted += inputBytes;
  492.         framesConverted += inputFrames;
  493.     }
  494.  
  495.     if (err == noErr) {
  496.         if (bytesConverted + inputBytes > length) {
  497.             inputBytes = length - bytesConverted;
  498.             inputFrames = totalFrames - framesConverted;
  499.         }
  500.         BlockMoveData (WAVEBuffer + bytesConverted, compressedBuf, inputBytes);
  501.         err = SoundConverterConvertBuffer (sc, compressedBuf, inputFrames, decomBuf2, &outputFrames, &outputBytes);
  502.         bytesConverted += inputBytes;
  503.         framesConverted += inputFrames;
  504.     }
  505.  
  506.     if (err == noErr) {
  507.         SoundCallBackFcnUPP = NewSndCallBackProc (SoundCallBackFcn);
  508.         err = SndNewChannel (&soundChan, sampledSynth, 0, SoundCallBackFcnUPP);
  509.     }
  510.  
  511.     if (err == noErr) {
  512.         WAVESndHeader1.samplePtr = decomBuf1;
  513.         WAVESndHeader1.numChannels = outputFormat.numChannels;
  514.         WAVESndHeader1.sampleRate = outputFormat.sampleRate;
  515.         WAVESndHeader1.loopStart = 0;
  516.         WAVESndHeader1.loopEnd = 0;
  517.         WAVESndHeader1.encode = cmpSH;
  518.         WAVESndHeader1.baseFrequency = kMiddleC;
  519.         WAVESndHeader1.numFrames = outputFrames;
  520.         WAVESndHeader1.AIFFSampleRate = 0;        // not used
  521.         WAVESndHeader1.markerChunk = nil;
  522.         WAVESndHeader1.format = outputFormat.format;
  523.         WAVESndHeader1.futureUse2 = 0;
  524.         WAVESndHeader1.stateVars = nil;
  525.         WAVESndHeader1.leftOverSamples = nil;
  526.         WAVESndHeader1.compressionID = fixedCompression;        // even uncompressed sounds use fixedCompression
  527.         WAVESndHeader1.packetSize = 0;            // the Sound Manager will figure this out for us
  528.         WAVESndHeader1.snthID = 0;
  529.         WAVESndHeader1.sampleSize = outputFormat.sampleSize;
  530.         WAVESndHeader1.sampleArea[0] = 0;        // no samples here because use samplePtr instead
  531.  
  532.         BlockMoveData (&WAVESndHeader1, &WAVESndHeader2, sizeof (WAVESndHeader1));
  533.         WAVESndHeader2.samplePtr = decomBuf2;            
  534.  
  535.         playCmd1.cmd = bufferCmd;
  536.         playCmd1.param1 = 0;                        // not used, but clear it out anyway just to be safe
  537.         playCmd1.param2 = (long)&WAVESndHeader1;
  538.  
  539.         playCmd2.cmd = bufferCmd;
  540.         playCmd2.param1 = 0;                        // not used, but clear it out anyway just to be safe
  541.         playCmd2.param2 = (long)&WAVESndHeader2;
  542.  
  543.         whichBuffer = 1;                            // buffer 1 will be free when callback runs
  544.         callCmd.cmd = callBackCmd;
  545.         callCmd.param2 = SetCurrentA5 ();
  546.  
  547.         gBufferDone = false;
  548.         err = SndDoCommand (soundChan, &playCmd1, true);
  549.     }
  550.  
  551.     if (err == noErr) {
  552.         err = SndDoCommand (soundChan, &callCmd, true);
  553.     }
  554.  
  555.     if (err == noErr) {
  556.         err = SndDoCommand (soundChan, &playCmd2, true);
  557.         gSoundPlaying = true;
  558.     }
  559.  
  560.     return err;
  561. }
  562.  
  563. OSErr InstallRequiredAppleEvents (void) {
  564.     OSErr        err;
  565.  
  566.     err = AEInstallEventHandler (kCoreEventClass, kAEOpenApplication, NewAEEventHandlerProc (HandleOApp), 0, false);
  567.  
  568.     if (err == noErr)
  569.         err = AEInstallEventHandler (kCoreEventClass, kAEOpenDocuments, NewAEEventHandlerProc (HandleODoc), 0, false);
  570.  
  571.     if (err == noErr)
  572.         err = AEInstallEventHandler (kCoreEventClass, kAEPrintDocuments, NewAEEventHandlerProc (HandlePDoc), 0, false);
  573.  
  574.     if (err == noErr)
  575.         err = AEInstallEventHandler (kCoreEventClass, kAEQuitApplication, NewAEEventHandlerProc (HandleQuit), 0, false);
  576.  
  577.     return err;
  578. }
  579.  
  580. pascal OSErr HandleOApp (AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon) {
  581. #pragma unused (theAppleEvent, reply, handlerRefcon)
  582.  
  583.     return noErr;                /* We're up and running */
  584. }
  585.  
  586. pascal OSErr HandleODoc (AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon) {
  587. #pragma unused (reply, handlerRefcon)
  588.  
  589.     AEDescList            docList;
  590.     OSErr                err;
  591.     long                i                = 1,
  592.                         itemsInList;
  593.     Size                actualSize;
  594.     AEKeyword            keywd;
  595.     DescType            returnedType;
  596.  
  597.     err = AEGetParamDesc (theAppleEvent, keyDirectObject, typeAEList, &docList);
  598.     if (err == noErr) {
  599.         err = AECountItems (&docList, &itemsInList);
  600.     }
  601.  
  602.     if (err == noErr) {
  603.         FSSpecPtr    fileSpecPtr;
  604.  
  605.         do {
  606.             fileSpecPtr = (FSSpecPtr)NewPtr (sizeof (FSSpec));
  607.             err = MemError ();
  608.  
  609.             if (err == noErr) {
  610.                 err = AEGetNthPtr (&docList, i, typeFSS, &keywd, &returnedType, fileSpecPtr, sizeof (FSSpec), &actualSize);
  611.             }
  612.  
  613.             if (err == noErr) {
  614.                 HParamBlockRec        pb;
  615.  
  616.                 pb.fileParam.ioCompletion = nil;
  617.                 pb.fileParam.ioNamePtr = fileSpecPtr->name;
  618.                 pb.fileParam.ioVRefNum = fileSpecPtr->vRefNum;
  619.                 pb.fileParam.ioDirID = fileSpecPtr->parID;
  620.                 pb.fileParam.ioFDirIndex = 0;
  621.  
  622.                 err = PBHGetFInfoSync (&pb);
  623.                 if (err == noErr && pb.fileParam.ioFlFndrInfo.fdType != 'pref') {
  624.                     err = PlaySound (fileSpecPtr);
  625.                     DisposePtr ((Ptr)fileSpecPtr);
  626.                 }
  627.             }
  628.  
  629.             i += 1;
  630.         } while (err == noErr);
  631.  
  632.         // The last time through the loop we allocate a pointer we don't need.
  633.         DisposePtr ((Ptr)fileSpecPtr);
  634.     }
  635.  
  636.     (void)AEDisposeDesc (&docList);
  637.  
  638.     return err;
  639. }
  640.  
  641. pascal OSErr HandlePDoc (AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon) {
  642. #pragma unused (theAppleEvent, reply, handlerRefcon)
  643.  
  644.     return noErr;
  645. }
  646.  
  647. pascal OSErr HandleQuit (AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon) {
  648. #pragma unused (theAppleEvent, reply, handlerRefcon)
  649.  
  650.     Quitting = true;
  651.     return noErr;
  652. }
  653.